/*
 *      Copyright (c) 2018-2028, Chill Zhuang All rights reserved.
 *
 *  Redistribution and use in source and binary forms, with or without
 *  modification, are permitted provided that the following conditions are met:
 *
 *  Redistributions of source code must retain the above copyright notice,
 *  this list of conditions and the following disclaimer.
 *  Redistributions in binary form must reproduce the above copyright
 *  notice, this list of conditions and the following disclaimer in the
 *  documentation and/or other materials provided with the distribution.
 *  Neither the name of the dreamlu.net developer nor the names of its
 *  contributors may be used to endorse or promote products derived from
 *  this software without specific prior written permission.
 *  Author: Chill 庄骞 (smallchill@163.com)
 */
package org.springblade.modules.repair.service.impl;

import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateUnit;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpException;
import cn.hutool.http.HttpUtil;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.extern.slf4j.Slf4j;
import org.springblade.common.cache.DictCache;
import org.springblade.common.config.ProjectApiConfiguration;
import org.springblade.common.utils.AsyncHttpUtil;
import org.springblade.common.utils.RobotUtil;
import org.springblade.core.launch.constant.TokenConstant;
import org.springblade.core.log.exception.ServiceException;
import org.springblade.core.mp.base.BaseEntity;
import org.springblade.core.mp.support.Query;
import org.springblade.core.redis.cache.BladeRedis;
import org.springblade.core.secure.utils.AuthUtil;
import org.springblade.core.tool.api.R;
import org.springblade.core.tool.utils.Func;
import org.springblade.core.tool.utils.StringPool;
import org.springblade.core.tool.utils.WebUtil;
import org.springblade.modules.notice.core.event.NoticeEvent;
import org.springblade.modules.notice.dto.NoticeTaskParam;
import org.springblade.modules.notice.enums.BusinessType;
import org.springblade.modules.notice.enums.TriggerConditionEnum;
import org.springblade.modules.notice.enums.TriggerNodeEnum;
import org.springblade.modules.repair.dto.MalfunctionRescueSaveDTO;
import org.springblade.modules.repair.dto.RescueArriveDTO;
import org.springblade.modules.repair.dto.RobotContentDTO;
import org.springblade.modules.repair.dto.plan.PlanAddUserDTO;
import org.springblade.modules.repair.dto.plan.TlwRescuePlanChangeStateDTO;
import org.springblade.modules.repair.entity.*;
import org.springblade.modules.repair.enums.*;
import org.springblade.modules.repair.service.*;
import org.springblade.modules.repair.utils.DistanceUtils;
import org.springblade.modules.repair.utils.MapUtil;
import org.springblade.modules.repair.vo.*;
import org.springblade.modules.repair.mapper.MaintainMalfunctionRescueMapper;
import org.springblade.core.mp.base.BaseServiceImpl;
import org.springblade.modules.repair.vo.plan.PlanRescueDetailVO;
import org.springblade.modules.repair.vo.rescue.*;
import org.springblade.modules.repair.wrapper.MaintainPlanContentWrapper;
import org.springblade.modules.system.entity.TenantConfig;
import org.springblade.modules.system.entity.User;
import org.springblade.modules.system.service.IUserService;
import org.springblade.modules.system.service.TenantConfigService;
import org.springblade.modules.system.vo.SimpleUserVO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Service;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.web.context.request.RequestContextHolder;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 救援工单明细 服务实现类
 *
 * @author ysq
 * @since 2023-02-25
 */
@Service
@Slf4j
public class MaintainMalfunctionRescueServiceImpl extends BaseServiceImpl<MaintainMalfunctionRescueMapper, MaintainMalfunctionRescue> implements IMaintainMalfunctionRescueService {

	@Resource
	private IMaintainPlanContentService maintainPlanContentService;
	@Autowired
	private IMaintainMalfunctionHistoryService malfunctionHistoryService;
	@Autowired
	private IMaintainPlanContentUserService maintainPlanContentUserService;
	@Autowired
	private IMaintainPlanContentExtService planContentExtService;
	@Autowired
	private IMaintainMalfunctionLogService maintainMalfunctionLogService;
	@Autowired
	private IMaintainSignService maintainSignService;
	@Autowired
	private IUserService userService;
	@Autowired
	private IBuildingService buildingService;
	@Autowired
	private IMaintainPlanContentExtService maintainPlanContentExtService;
	@Autowired
	private IMaintainMalfunctionLogService malfunctionLogService;
	@Autowired
	private AsyncHttpUtil asyncHttpUtil;
	@Autowired
	private ProjectApiConfiguration projectApiConfiguration;
	@Autowired
	private IElevatorService elevatorService;
	@Autowired
	private IBuildingExtService buildingExtService;
	@Autowired
	private IElevatorStopLogService elevatorStopLogService;
	@Autowired
	private RobotUtil robotUtil;
	@Autowired
	private TenantConfigService tenantConfigService;
	@Autowired
	private ElevatorPrincipalDeptService elevatorPrincipalDeptService;
	@Autowired
	private IMaintainMalfunctionWorkService maintainMalfunctionWorkService;
	@Autowired
	private ApplicationEventPublisher eventPublisher;
	@Autowired
	private BladeRedis bladeRedis;

	/**
	 * redis催办key
	 */
	private static final String RESCUE_REMINDER_KEY = "rescue:reminder:key";

	@Override
	public IPage<MalfunctionRescueHistoryPageVO> selectMaintainMalfunctionRescueHistoryPage(IPage<MalfunctionRescueHistoryPageVO> page, Long elevatorId) {
		return page.setRecords(baseMapper.selectMaintainMalfunctionRescueHistoryPage(page, elevatorId));
	}

	/**
	 * 2025-09 拆分历史记录，新增传参 isSearchHistory
	 * @param userId 用户id
	 * @param longitude 经度
	 * @param latitude 纬度
	 * @return List<MalfunctionRescueHomeListVO> 结果
	 */
	@Override
	public List<MalfunctionRescueHomeListVO> malfunctionRescueHomeList(Long userId, String longitude, String latitude, Date startDate, Date endDate, Boolean sortByPlanDateASC) {
		if(Func.isEmpty(sortByPlanDateASC)){
			sortByPlanDateASC = Boolean.FALSE;
		}
		List<MalfunctionRescueHomeListVO> list;
		if(Func.isNotEmpty(startDate) && Func.isNotEmpty(endDate)) {
			list = baseMapper.malfunctionRescueHomeList(userId, startDate, endDate, sortByPlanDateASC);
		}else{
			startDate = null;
			endDate = null;
			list = baseMapper.malfunctionRescueHomeList(userId, startDate, endDate, sortByPlanDateASC);
		}

		if (ObjectUtil.isNotEmpty(list)) {
			Date serviceDate = DateUtil.date();
			list.forEach(e -> {
				if (StrUtil.isNotEmpty(latitude) && StrUtil.isNotEmpty(longitude)) {
					BigDecimal distance = DistanceUtils.getDistance(Convert.toDouble(longitude), Convert.toDouble(latitude), Convert.toDouble(e.getLongitude()), Convert.toDouble(e.getLatitude()));
					final String kmDesc = distance.divide(Convert.toBigDecimal(1000), 1, BigDecimal.ROUND_HALF_UP).toString();
					e.setKmDesc(kmDesc + "km");
				}
				e.getList().forEach(j -> {
					j.setServiceDate(serviceDate);
				});
			});
		}
		return list;
	}

	@Override
	public MalfunctionRescueDetailVO malfunctionRescueDetail(Long planId) {

		// 查询救援详情
		MalfunctionRescueDetailVO detailVO = baseMapper.malfunctionRescueDetail(planId);

		// 处理救援记录 救援方式
		if (ObjectUtil.isNotEmpty(detailVO.getRescueModeVO())) {
			List<String> recueModeList = new ArrayList<>();
			if (ObjectUtil.isNotEmpty(detailVO.getRescueModeVO().getRescueModeValue())) {
				recueModeList.addAll(Func.toStrList(detailVO.getRescueModeVO().getRescueModeValue()));
			}
			detailVO.getRescueModeVO().setRescueModeList(recueModeList);
		}

		// 处理停梯原因
		if (ObjectUtil.isNotEmpty(detailVO.getRescueStopReasonVO())) {
			List<String> recueStopReasonList = new ArrayList<>();
			if (ObjectUtil.isNotEmpty(detailVO.getRescueStopReasonVO().getStopReasonValue())) {
				recueStopReasonList.addAll(Func.toStrList(detailVO.getRescueStopReasonVO().getStopReasonValue()));
			}
			detailVO.getRescueStopReasonVO().setRecueStopReasonList(recueStopReasonList);
		}

		// 查询该任务电梯最近一次完成的任务
		LastPlanVO lastPlanVO = maintainPlanContentService.selectElevatorLastPlan(detailVO.getElevatorId(), planId);
		detailVO.setLastPlan(lastPlanVO);

		// 查询关联维修任务
		List<RelevanceMalfunctionPlanVO> relevanceMalfunctionPlanVO = baseMapper.selectRelevanceMalfunctionPlan(planId);
		if (ObjectUtil.isNotEmpty(relevanceMalfunctionPlanVO)) {
			relevanceMalfunctionPlanVO.forEach(e -> {
				if(Func.isNotEmpty(e.getFaultMessageList())){
					e.getFaultMessageList().forEach(j -> {
						if(Func.isNotEmpty(j.getProblemItem())){
							List<String> problemKeys = Func.toStrList(e.getProblemItem());
							List<String> problemNames = new ArrayList<>();
							for (String problemKey : problemKeys) {
								String problemName = DictCache.getValue("fault_problem", problemKey);
								problemNames.add(problemName);
							}
							j.setProblemItemNames(problemNames);
						}
					});
				}
			});
		}
		detailVO.setRelevanceMalfunctionPlanVO(relevanceMalfunctionPlanVO);

		// 查询维修操作记录
		List<MalFunctionHistoryVO> malFunctionHistories = malfunctionHistoryService.selectByPlanId(planId);
		detailVO.setMalFunctionHistories(malFunctionHistories);
		// 查询关联人员信息
//		List<MaintainPlanContentUser> planContentUsers = maintainPlanContentUserService.list(Wrappers.<MaintainPlanContentUser>lambdaQuery().eq(MaintainPlanContentUser::getPlanId, planId));
		List<MaintainPlanContentUser> planContentUsers = maintainPlanContentUserService.listUserByPlan(planId);
		detailVO.setPlanContentUsers(planContentUsers);

		detailVO.setUpdateStatus(Boolean.TRUE);
		// 完成工单后24小时内可以上传检修记录
		if (detailVO.getStatus().equals(TaskStatusEnum.JY_RESCUE.getStatus())) {
			MaintainPlanContentExt planContentExt = planContentExtService.getOne(Wrappers.<MaintainPlanContentExt>lambdaQuery().eq(MaintainPlanContentExt::getPlanId, planId));
			long betweenHour = DateUtil.between(planContentExt.getPlanEndTime(), DateUtil.date(), DateUnit.HOUR);
			if (betweenHour > 24) {
				detailVO.setUpdateStatus(Boolean.FALSE);
			}
		}

//		if(!detailVO.getStatus().equals(TaskStatusEnum.JY_RESCUE.getStatus()) && !detailVO.getStatus().equals(TaskStatusEnum.JY_CANCEL.getStatus())){
//			// 更新人员签到状态
//			maintainPlanContentUserService.updatePlanUserSignInfo(planId, SignEnum.SIGN.getId(), AuthUtil.getUserId(), SignErrorTypeEnum.NORMAL.getId());
//		}

		// 当前用户是否已签到
		Long currentUser = AuthUtil.getUserId();
		List<MaintainSign> list = maintainSignService.list(new LambdaQueryWrapper<MaintainSign>()
			.eq(MaintainSign::getPlanId, planId)
			.eq(MaintainSign::getUserId, currentUser)
		);
		detailVO.setIsSign(list.size() > 0);
		return detailVO;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public Boolean malfunctionRescueFinish(MalfunctionRescueSaveDTO saveDTO) {
		// 更新工单关联电梯信息
		MaintainPlanContent planContent = maintainPlanContentService.getById(saveDTO.getPlanId());
		if (planContent.getStatus().equals(TaskStatusEnum.JY_RESCUE.getStatus())) {
			throw new ServiceException("工单状态已变更，请刷新页面");
		}
		if (!planContent.getStatus().equals(TaskStatusEnum.JY_ARRIVE.getStatus())) {
			throw new ServiceException("工单状态异常，请刷新页面");
		}

		// 记录报障时电梯id，后续判断有无更改电梯
		Long oldElevatorId = planContent.getElevatorId();
		planContent.setElevatorId(saveDTO.getElevatorId());
		planContent.setStatus(TaskStatusEnum.JY_RESCUE.getStatus());
		maintainPlanContentService.updateById(planContent);

		// 更新工单用户的签到信息，并返回实际作业人
		List<MaintainPlanContentUser> workPlanUsers = maintainPlanContentUserService.updateRescuePlanUserSignInfo(saveDTO.getPlanId());

		MaintainPlanContentExt planContentExt = planContentExtService.getOne(Wrappers.<MaintainPlanContentExt>lambdaQuery().eq(MaintainPlanContentExt::getPlanId, saveDTO.getPlanId()));
		long workMinute = DateUtil.between(planContentExt.getPlanBeginTime(), DateUtil.date(), DateUnit.MINUTE);
		planContentExt.setWorkMinute((int) workMinute);
		planContentExt.setPlanEndTime(DateUtil.date());
		// 非电梯部门工单处理人ids
		ElevatorPrincipalDept elevatorPrincipalDept = elevatorPrincipalDeptService.getOne(Wrappers.<ElevatorPrincipalDept>lambdaQuery().eq(ElevatorPrincipalDept::getElevatorId, planContent.getElevatorId()), false);
		if (Func.isNotEmpty(workPlanUsers) && Func.isNotEmpty(elevatorPrincipalDept)) {
			List<Long> workUserIds = workPlanUsers.stream().map(e -> e.getUserId()).collect(Collectors.toList());
			List<User> workUserInfos =userService.listByIds(workUserIds);
			String notEleDeptWorkUsers = workUserInfos.stream().filter(user -> {
				List<Long> deptIds = Func.toLongList(user.getDeptId());
				return !deptIds.contains(elevatorPrincipalDept.getDeptId());
			}).map(t -> String.valueOf(t.getId())).collect(Collectors.joining( ","));
			//只有当值不为空时才设置
			Optional.of(notEleDeptWorkUsers).filter(StrUtil::isNotBlank).ifPresent(planContentExt::setNotEleDeptWorkUsers);
		}
		planContentExtService.updateById(planContentExt);


		// 插入工单记录
		maintainMalfunctionLogService.insertWorkLog(saveDTO.getPlanId(), TaskStatusEnum.getTaskName(planContent.getTypeStatus(), planContent.getStatus()), planContent.getStatus());

		MaintainMalfunctionRescue malfunctionRescue = super.getOne(Wrappers.<MaintainMalfunctionRescue>lambdaQuery().eq(MaintainMalfunctionRescue::getPlanId, saveDTO.getPlanId()));
		malfunctionRescue.setAddress(saveDTO.getElevatorAddress());
		malfunctionRescue.setRescueMode(saveDTO.getRecueMode());
		malfunctionRescue.setRescueModeValue(saveDTO.getRecueModeValue());
		malfunctionRescue.setRescueModeRemark(saveDTO.getRecueModeRemark());
		malfunctionRescue.setRescueModeDesc(saveDTO.getRecueModeDesc());
		malfunctionRescue.setImgUrl(saveDTO.getImgUrl());
		malfunctionRescue.setStopReason(saveDTO.getStopReason());
		malfunctionRescue.setStopReasonValue(saveDTO.getStopReasonValue());
		malfunctionRescue.setStopReasonRemark(saveDTO.getStopReasonRemark());
		malfunctionRescue.setStopReasonDesc(saveDTO.getStopReasonDesc());
		malfunctionRescue.setElevatorId(saveDTO.getElevatorId());
		boolean result = super.updateById(malfunctionRescue);
		if (result) {
			boolean mqFlag = true;
			RescueOrderDetail rescueOrderDetail = this.rescueDetail(saveDTO.getPlanId());
			if(Func.isNull(rescueOrderDetail)){
				mqFlag = false;
			}
			if (!Func.equalsSafe(oldElevatorId, planContent.getElevatorId())) {
				String deviceId = baseMapper.getDeviceIdByElevatorId(oldElevatorId);
				if (Func.isNotBlank(deviceId)) {
					rescueOrderDetail = new RescueOrderDetail();
					rescueOrderDetail.setDeviceId(deviceId);
					rescueOrderDetail.setPlanId(planContent.getId());
					rescueOrderDetail.setElevatorId(oldElevatorId);
					rescueOrderDetail.setStatus(TaskStatusEnum.JY_CANCEL.getStatus());
					rescueOrderDetail.setStatusDate(planContent.getUpdateTime());
					rescueOrderDetail.setRescueId(rescueOrderDetail.getRescueId());
				} else {
					mqFlag = false;
				}
			}

			if (mqFlag) {
				rescueOrderDetail.setWorkMinute((int) workMinute);
				// 同步工单状态到梯联网
				TlwRescuePlanChangeStateDTO changeStateParam = new TlwRescuePlanChangeStateDTO(rescueOrderDetail.getElevatorId(), rescueOrderDetail.getDeviceId(), rescueOrderDetail.getStatus(), rescueOrderDetail.getPlanId());
				RescueOrderDetail finalRescueOrderDetail = rescueOrderDetail;
				// 使用事务事件监听器，在事务提交后执行
				TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
					@Override
					public void afterCommit() {
						// 发送救援信息到MQ
						asyncHttpUtil.createPost(projectApiConfiguration.getWeb() + "/admin/mq/message/sendMessage", JSONObject.toJSONString(finalRescueOrderDetail, SerializerFeature.WRITE_MAP_NULL_FEATURES), WebUtil.getRequest());
						asyncHttpUtil.createPost(projectApiConfiguration.getWeb() + "/estate/tlw/screen/rescue/plan/change/state", Func.toJson(changeStateParam), WebUtil.getRequest());
					}
				});
			}
		}
		// 2023.09.20 新增新字段更新
		boolean newFlied = updateExtNewFlied(planContentExt);

		Elevator elevator = elevatorService.getById(planContent.getElevatorId());
		if(ObjectUtil.isNotEmpty(elevator) && ObjectUtil.isNotEmpty(elevator.getIsStop())){
			if(!elevator.getIsStop().equals(saveDTO.getIsStop())){
				ElevatorStopLog elevatorStopLog = new ElevatorStopLog();
				elevatorStopLog.setElevatorId(planContent.getElevatorId());
				elevatorStopLog.setInitStatus(elevator.getIsStop());
				elevatorStopLog.setSetStatus(saveDTO.getIsStop());
				elevatorStopLog.setModule(String.valueOf(planContent.getId()));
				elevatorStopLog.setPosition(2);
				elevatorStopLogService.insertStopLog(elevatorStopLog);

				elevator.setIsStop(saveDTO.getIsStop());
				elevatorService.updateById(elevator);
			}
		}
		// 2024-08-19 新增 救援完成微信卡片消息
		RobotContentDTO robotContentDTO = new RobotContentDTO();
		robotContentDTO.setMaintainType(MaintainPlanContentEnum.MAINTAIN_KR.getId());
		robotContentDTO.setMaintainSubTypeDesc("【救援完成】");
		ElevatorRescueRobotSimpleVO elevatorInfo = maintainPlanContentService.getElevatorSimpleInfoByPlanId(saveDTO.getPlanId());
		robotContentDTO.setLocationDesc(elevatorInfo.getBuildingName() + "-" + elevatorInfo.getElevatorAddress());
		// 复用 malfunctionDesc 字段 存放 故障信息
		robotContentDTO.setMalfunctionDesc(saveDTO.getStopReasonValue());
		List<String> userName = new ArrayList<>();
		List<String> avatars = new ArrayList<>();
		// 获取当前项目计划执行人
		List<Long> firstAddUsers = maintainPlanContentUserService.list(new LambdaQueryWrapper<MaintainPlanContentUser>()
			.eq(MaintainPlanContentUser::getPlanId, saveDTO.getPlanId())
			.eq(BaseEntity::getIsDeleted, 0)
		).stream().map(MaintainPlanContentUser::getUserId).collect(Collectors.toList());
		firstAddUsers.forEach(f -> {
			SimpleUserVO simpleUserInfo = userService.getSimpleUserInfo(f);
			userName.add(simpleUserInfo.getUserName());
			avatars.add(simpleUserInfo.getAvatar());
		});
		robotContentDTO.setMaintainPersonDesc(String.join(",", userName));
		robotContentDTO.setMaintainPersonImgDesc(String.join(",", avatars));
		robotUtil.sendRobotMessage(RequestContextHolder.getRequestAttributes(), planContent.getElevatorId(), saveDTO.getPlanId(), robotContentDTO);
		return result && newFlied;
	}

	@Override
	@Transactional(rollbackFor = Exception.class)
	public boolean rescueConfirm(RescueArriveDTO arriveRequest) {
		// 获取用户信息
		User confirmUser = userService.getById(arriveRequest.getUserId());
		if (Func.isNull(confirmUser)) {
			throw new ServiceException("确认失败，救援人员不存在！");
		}

		// 获取工单信息
		MaintainPlanContent confirmPlan = maintainPlanContentService.getOne(
			new LambdaQueryWrapper<MaintainPlanContent>()
				.eq(BaseEntity::getId, arriveRequest.getPlanId())
		);
		if (Func.isNull(confirmPlan)) {
			throw new ServiceException("确认失败，该救援工单不存在,请先创建救援工单");
		}
		// 当且仅当 工单状态为3 的时候，才能变成已到达（防止已撤销的情况）
		Integer status = confirmPlan.getStatus();
		boolean flag = status.equals(TaskStatusEnum.JY_PROCESSED.getStatus()) || status.equals(TaskStatusEnum.JY_ARRIVE.getStatus());
		if (!flag) {
			throw new ServiceException("已到达失败，只有状态为已出发才能已到达！");
		}
		// 2024-08-20 新增 若工单已为已到达，则不需要重复发送消息
		boolean pushCardMessage = !status.equals(TaskStatusEnum.JY_ARRIVE.getStatus());
		// 获取工单明细对应的电梯、楼盘信息，经纬度
		List<MaintainMalfunctionRescue> rescuesList = this.list(
			Wrappers.<MaintainMalfunctionRescue>lambdaQuery()
				.eq(MaintainMalfunctionRescue::getPlanId, arriveRequest.getPlanId())
				.eq(BaseEntity::getIsDeleted, 0)
		);
		if (Func.isEmpty(rescuesList) || rescuesList.size() != 1) {
			throw new ServiceException("确认失败！请确认救援信息是否正确");
		}
		MaintainMalfunctionRescue confirmRescue = rescuesList.get(0);
		if (Func.isNull(confirmRescue)) {
			throw new ServiceException("确认失败！请确认救援信息是否正确");
		}
		// 获取项目经纬度、地址
		Long buildingId = confirmRescue.getBuildingId();
		if (Func.isNull(buildingId)) {
			throw new ServiceException("确认失败！救援工单未指定项目楼盘！");
		}
		// 2. 用户签到：
		List<MaintainSign> signList = maintainSignService.list(new LambdaQueryWrapper<MaintainSign>()
			.eq(MaintainSign::getPlanId, arriveRequest.getPlanId())
			.eq(MaintainSign::getUserId, arriveRequest.getUserId())
		);
		if (signList.size() > 0) {
			throw new ServiceException("点击已到达时后台会默认签到，救援工单不能重复签到！");
		}

		MaintainSign arriveSign = signProperties(arriveRequest);
		// 签到
		boolean sign = maintainSignService.save(arriveSign);
		maintainPlanContentUserService.updatePlanUserSignInfo(arriveRequest.getPlanId(), SignEnum.SIGN.getId(), arriveRequest.getUserId(), SignErrorTypeEnum.NORMAL.getId());
		// 3. 当前工单状态改为 4=已到达
		boolean changeStatus = false;
		// 若当前工单状态已经为已到达，不变
		if (TaskStatusEnum.JY_ARRIVE.getStatus() == confirmPlan.getStatus()) {
			changeStatus = true;
		} else {
			// 不为已到达，工单更新为已到达
			confirmPlan.setStatus(TaskStatusEnum.JY_ARRIVE.getStatus());
			changeStatus = maintainPlanContentService.updateById(confirmPlan);
			if (!changeStatus) {
				throw new ServiceException("确认失败，系统异常，无法更改救援状态为已到达，请联系系统管理员");
			}
		}
		// 4. 工单log记录表插入救援状态转换记录
		MaintainMalfunctionLog workTracks = new MaintainMalfunctionLog();
		workTracks.setExecutorId(AuthUtil.getUserId());
		workTracks.setExecutor(AuthUtil.getNickName());
		workTracks.setPlanId(arriveRequest.getPlanId());
		workTracks.setPlanStatus(TaskStatusEnum.JY_ARRIVE.getStatus());
		workTracks.setTypeName(TaskStatusEnum.JY_ARRIVE.getName());
		JSONObject errorLogExt = new JSONObject();
		errorLogExt.put("signStatus", arriveSign.getSignStatus());
		errorLogExt.put("locationAddress", arriveRequest.getLocationAddress());
		workTracks.setExt(errorLogExt.toJSONString());
		boolean saveLog = malfunctionLogService.save(workTracks);
		// 5. 拓展表插入已到达时间
		Date arriveTime = new Date();
		MaintainPlanContentExt confirmExt = maintainPlanContentExtService.getOne(
			new LambdaQueryWrapper<MaintainPlanContentExt>()
				.eq(MaintainPlanContentExt::getPlanId, arriveRequest.getPlanId())
		);
		if (Func.isNull(confirmExt)) {
			confirmExt = new MaintainPlanContentExt();
			confirmExt.setPlanId(arriveRequest.getPlanId());
		}
		if (Func.isNull(confirmExt.getArriveTime())) {
			confirmExt.setArriveTime(arriveTime);
			confirmExt.setArriveDuration(arriveTime.getTime() - confirmPlan.getCreateTime().getTime());
			maintainPlanContentExtService.saveOrUpdate(confirmExt);
		}

		// 6. 若超时到达，插入工单log
		if (DateUtil.compare(arriveTime, confirmRescue.getMaximumArrivalTime(), DatePattern.NORM_DATETIME_MINUTE_PATTERN) > 0) {
			// 多人作业，工单超时到达标签只打一次
			if (confirmRescue.getWarningList() == null || !Func.toIntList(confirmRescue.getWarningList()).contains(RescueArriveEnum.OVERTIME_ARRIVE.getId())) {
				confirmRescue.setArrivalWarning(RescueArriveEnum.OVERTIME_ARRIVE.getId());
				confirmRescue.setArrivalWarningTime(arriveTime);
				List<Integer> oldWarningList = new ArrayList<>();
				if (Func.isNotBlank(confirmRescue.getWarningList())) {
					oldWarningList = Arrays.stream(confirmRescue.getWarningList().split(","))
						.filter(s -> Func.isNotBlank(s)) // 过滤空字符串
						.map(String::trim) // 去除前后空格
						.map(Integer::parseInt) // 转换为 Integer
						.collect(Collectors.toList());
				}
				oldWarningList.add(RescueArriveEnum.OVERTIME_ARRIVE.getId());
				confirmRescue.setWarningList(oldWarningList.stream().distinct().map(e -> String.valueOf(e)).collect(Collectors.joining( ",")));
				this.updateById(confirmRescue);

				JSONObject logExt = new JSONObject();
				logExt.put("bType", RescueArriveEnum.OVERTIME_ARRIVE.getId());
				logExt.put("timeOut", DateUtil.formatDateTime(confirmRescue.getMaximumArrivalTime()));
				malfunctionLogService.insertWorkLog(arriveRequest.getPlanId(), WorkTrackStatusEnum.OVERTIME_ARRIVE.getTypeName(),  TaskStatusEnum.JY_ARRIVE.getStatus(), logExt.toJSONString());

				// 消息通知
				eventPublisher.publishEvent(new NoticeEvent(NoticeTaskParam.builder()
					.businessType(BusinessType.RESCUE.getCode())
					.triggerNode(TriggerNodeEnum.SIGN_IN.getCode())
					.triggerCondition(TriggerConditionEnum.CONDITION_25.getCode())
					.bizId(arriveRequest.getPlanId())
					.tenantId(AuthUtil.getTenantId())
					.build()
				));
			}
		}

		// 发送救援信息到MQ
		RescueOrderDetail rescueOrderDetail = this.rescueDetail(arriveRequest.getPlanId());
		if (Func.isNotEmpty(rescueOrderDetail)) {
			rescueOrderDetail.setMapUserInfoDetails(this.mapUserDetails(arriveRequest.getPlanId()));
			TlwRescuePlanChangeStateDTO changeStateParam = new TlwRescuePlanChangeStateDTO(rescueOrderDetail.getElevatorId(), rescueOrderDetail.getDeviceId(), rescueOrderDetail.getStatus(), rescueOrderDetail.getPlanId());
			// 使用事务事件监听器，在事务提交后执行
			TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
				@Override
				public void afterCommit() {
					// 推送救援信息到梯联网
					asyncHttpUtil.createPost(projectApiConfiguration.getWeb() + "/admin/mq/message/sendMessage", JSONObject.toJSONString(rescueOrderDetail, SerializerFeature.WRITE_MAP_NULL_FEATURES), WebUtil.getRequest());
					// 同步工单状态到梯联网
					asyncHttpUtil.createPost(projectApiConfiguration.getWeb() + "/estate/tlw/screen/rescue/plan/change/state", Func.toJson(changeStateParam), WebUtil.getRequest());
				}
			});
		}
		// 2024-08-19 新增发送【救援已到达】机器人微信群小程序卡片
		// 电梯已知的情况下发送，且多人到达的情况下只发一次
		if(confirmPlan.getElevatorId()>0 && pushCardMessage){
			RobotContentDTO robotContentDTO = new RobotContentDTO();
			robotContentDTO.setMaintainType(MaintainPlanContentEnum.MAINTAIN_KR.getId());
			robotContentDTO.setMaintainSubTypeDesc("【救援已到达】");
			ElevatorRescueRobotSimpleVO elevatorInfo = maintainPlanContentService.getElevatorSimpleInfoByPlanId(arriveRequest.getPlanId());
			robotContentDTO.setLocationDesc(elevatorInfo.getBuildingName() + "-" + elevatorInfo.getElevatorAddress());
			// 复用 malfunctionDesc 字段 存放 到达信息
			robotContentDTO.setMalfunctionDesc(elevatorInfo.getRescueTimeDesc());
			List<String> userName = new ArrayList<>();
			List<String> avatars = new ArrayList<>();
			// 获取当前项目计划执行人
			List<Long> firstAddUsers = maintainPlanContentUserService.list(new LambdaQueryWrapper<MaintainPlanContentUser>()
				.eq(MaintainPlanContentUser::getPlanId, arriveRequest.getPlanId())
				.eq(BaseEntity::getIsDeleted, 0)
			).stream().map(MaintainPlanContentUser::getUserId).collect(Collectors.toList());

			firstAddUsers.forEach(f -> {
				SimpleUserVO simpleUserInfo = userService.getSimpleUserInfo(f);
				userName.add(simpleUserInfo.getUserName());
				avatars.add(simpleUserInfo.getAvatar());
			});
			robotContentDTO.setMaintainPersonDesc(String.join(",", userName));
			robotContentDTO.setMaintainPersonImgDesc(String.join(",", avatars));
			robotUtil.sendRobotMessage(RequestContextHolder.getRequestAttributes(), confirmPlan.getElevatorId(), arriveRequest.getPlanId(), robotContentDTO);
		}
		return sign && saveLog;
	}

	/**
	 * 方法大于80行，赋值行为抽取
	 *
	 * @return 赋值后的签到对象
	 */
	private MaintainSign signProperties(RescueArriveDTO arriveRequest) {
		MaintainSign arriveSign = new MaintainSign();
		Date signTime = new Date();
		arriveSign.setPlanId(arriveRequest.getPlanId());
		arriveSign.setUserId(arriveRequest.getUserId());
		// 签到信息：经度、纬度、地址、状态、系统代签、签到时间
		arriveSign.setSignLongitude(arriveRequest.getLongitude());
		arriveSign.setSignLatitude(arriveRequest.getLatitude());
		arriveSign.setSignAddress(arriveRequest.getLocationAddress());
		arriveSign.setSignStatus(arriveRequest.getSignStatus());
		arriveSign.setSignTime(signTime);
		arriveSign.setStatus(2);
		// 签退信息：签退经度、纬度、地址、状态、间隔、签退时间
		arriveSign.setSignOutLongitude(arriveRequest.getLongitude());
		arriveSign.setSignOutLatitude(arriveRequest.getLatitude());
		arriveSign.setSignOutAddress(arriveRequest.getLocationAddress());
		arriveSign.setTimeInterval(0L);
		arriveSign.setSignOutStatus(arriveRequest.getSignStatus());
		arriveSign.setSignOutTime(signTime);
		return arriveSign;
	}


	@Override
	public List<MapUserInfoDetailVO> mapUserDetails(Long planId) {
		List<MapUserInfoDetailVO> details = baseMapper.mapUserDetails(planId);

		// 救援人员到达情况
		List<Long> userIds = details.stream().map(t -> t.getUserId()).collect(Collectors.toList());
		if (Func.isNotEmpty(userIds)) {
			List<MaintainSign> maintainSigns = maintainSignService.list(Wrappers.<MaintainSign>lambdaQuery().in(MaintainSign::getUserId, userIds).eq(MaintainSign::getPlanId, planId));
			Map<Long, MaintainSign> signMap = maintainSigns.stream().collect(Collectors.groupingBy(t -> t.getUserId(), Collectors.collectingAndThen(Collectors.toList(), t -> t.get(0))));

			for (MapUserInfoDetailVO userInfo : details) {
				if (signMap.containsKey(userInfo.getUserId())) {
					// 用户已到达
					MaintainSign maintainSign = signMap.get(userInfo.getUserId());
					userInfo.setArrived(Boolean.TRUE);
					userInfo.setCreateTime(maintainSign.getSignTime());
					userInfo.setLongitude(new BigDecimal(maintainSign.getSignLongitude()));
					userInfo.setLatitude(new BigDecimal(maintainSign.getSignLatitude()));
				} else {
					userInfo.setArrived(Boolean.FALSE);
				}
			}
		}

		return details;
	}

	@Override
	public RescueOrderDetail rescueDetail(Long planId) {
		return baseMapper.rescueDetail(planId);
	}

	@Override
	public boolean rescueUserRunning(Long userId) {
		return baseMapper.rescueUserRunning(userId) == null ? Boolean.FALSE : Boolean.TRUE;
	}

	@Override
	public Map<String, Object> getElevatorOfRescueInfo(Integer type, Long elevatorId) {
		Map<String, Object> resultMap = new HashMap<>(2);
		List<ElevatorOfRescueDateVo> resultList = new ArrayList<>(16);
		List<ElevatorOfRescueVo> list = baseMapper.getElevatorOfRescueInfo(type, elevatorId);
		if (list.size() == 0) {
			resultMap.put("title", String.format("已救援(%d)", list.size()));
			resultMap.put("total", list.size());
			resultMap.put("list", resultList);
			return resultMap;
		}
		Map<String, List<ElevatorOfRescueVo>> listMap = list.stream().collect(Collectors.groupingBy(ElevatorOfRescueVo::getGroupDate));
		List<String> strings = list.stream().map(ElevatorOfRescueVo::getGroupDate).distinct().collect(Collectors.toList());
		for (String date : strings) {
			List<ElevatorOfRescueVo> elevatorOfRescueVos = listMap.get(date);
			for (ElevatorOfRescueVo elevator : elevatorOfRescueVos) {
				List<MaintainPlanContentUser> maintainPlanContentUsers = maintainPlanContentUserService.listUserByPlan(elevator.getPlanId());
				elevator.setPlanContentUserVos(maintainPlanContentUsers);
			}
			ElevatorOfRescueDateVo vo = new ElevatorOfRescueDateVo();
			vo.setPlanDate(date);
			vo.setDataList(elevatorOfRescueVos);
			resultList.add(vo);
		}
		resultMap.put("title", String.format("已完成救援(%d)", list.size()));
		resultMap.put("total", list.size());
		resultMap.put("list", resultList);
		return resultMap;
	}

	private boolean updateExtNewFlied(MaintainPlanContentExt confirmExt) {
		// 0. 完整性判断
		if (Func.isNull(confirmExt.getArriveTime())) {
			log.info("该工单可能存在异常，需要插入已到达时间但未插入，请详细查询，工单id{}", confirmExt.getPlanId());
			return false;
		}
		// 1. 判断 当前confirmExt 是否已存在是否超时等字段，否：执行插入并更新逻辑
		if (Func.isNull(confirmExt.getArriveDuration())) {
			long createTime = maintainPlanContentService.getById(confirmExt.getPlanId()).getCreateTime().getTime();
			long arriveDuration = confirmExt.getArriveTime().getTime() - createTime;
			if(arriveDuration < 0){
				arriveDuration = 0;
			}
			confirmExt.setArriveDuration(arriveDuration);
		}

		// 2. 根据是否超时插入是否超时：1->铁定大于30分钟 2->小于30分钟且大于对应白天或晚上时间
		// 阈值
		boolean isSuccess = true;
		long minutes = 30;
		long door = TimeUnit.MINUTES.toMillis(minutes);
		// 铁定大于30分钟
		Long arriveMillisecond = confirmExt.getArriveDuration();
		if (arriveMillisecond > door) {
			confirmExt.setIsOvertime(1);
		} else {
			// 小于30分钟，且未设置自定义，或者自定义为空
			Long planId = confirmExt.getPlanId();
			MaintainPlanContent plan = maintainPlanContentService.getById(planId);
			Long elevatorId = plan.getElevatorId();
			Elevator elevator = elevatorService.getById(elevatorId);
			Long buildingId = elevator.getBuildingId();
			BuildingExt buildingExt = buildingExtService.getOne(new LambdaQueryWrapper<BuildingExt>()
				.eq(BuildingExt::getBuildingId, buildingId)
				.eq(BaseEntity::getIsDeleted, 0)
			);
			Integer rescueTime = buildingExt.getRescueTime();
			if (Func.isNull(rescueTime) || rescueTime == 0) {
				confirmExt.setIsOvertime(3);
			} else if (rescueTime == 1) {
				// 救援工单列表到达时间-开始时间 < 30 分钟，且该项目有自定义设置时间 先判断白天或黑夜
				// 创建一个 Calendar 对象，并设置未工单创建时间
				Calendar calendar = Calendar.getInstance();
				calendar.setTime(plan.getCreateTime());
				// 获取当前时间的小时
				int hour = calendar.get(Calendar.HOUR_OF_DAY);
				// 判断时间是否在 6:00 ~ 18:00 时间段内
				boolean isWithinTimeRange = hour >= 6 && hour < 18;
				if (isWithinTimeRange) {
					Integer rescueDaytime = buildingExt.getRescueDaytime();
					long dayDoor = TimeUnit.MINUTES.toMillis(rescueDaytime);
					if (arriveMillisecond <= dayDoor) {
						confirmExt.setIsOvertime(3);
					} else {
						confirmExt.setIsOvertime(2);
					}
				} else {
					// 白天或黑夜超时，该工单是否延迟设置2，不超时，设置延迟3
					Integer rescueNight = buildingExt.getRescueNight();
					long nightDoor = TimeUnit.MINUTES.toMillis(rescueNight);
					if (arriveMillisecond <= nightDoor) {
						confirmExt.setIsOvertime(3);
					} else {
						confirmExt.setIsOvertime(2);
					}
				}
			} else {
				throw new ServiceException("业务异常或业务发生变更，请联系管理员");
			}
		}

		// 3. 补充ext新字段
		if (confirmExt.getPlanBeginTime() != null && confirmExt.getPlanEndTime() != null) {
			long beginTime = confirmExt.getPlanBeginTime().getTime();
			long endTime = confirmExt.getPlanEndTime().getTime();
			long workMill = endTime - beginTime;
			confirmExt.setWorkMill(workMill);
		}
		Long planId = confirmExt.getPlanId();
		MaintainPlanContent currentPlan = maintainPlanContentService.getById(planId);
		Date createTime = currentPlan.getCreateTime();
		// 当分配时间不为空时，直接计算
		if (!Func.isNull(confirmExt.getAllocateTime())) {
			long allocateDuration = confirmExt.getAllocateTime().getTime() - createTime.getTime();
			confirmExt.setAllocateDuration(allocateDuration);
		} else {
			// 当分配时间为空时，拿用户关联表最快分配到的时间，计算，获得allocateTime
			MaintainPlanContentUser theFastUser = baseMapper.getTheFastUser(planId);
			if (!Func.isNull(theFastUser)) {
				Date getTime = theFastUser.getCreateTime();
				confirmExt.setAllocateTime(getTime);
				long allocateDuration = confirmExt.getAllocateTime().getTime() - createTime.getTime();
				confirmExt.setAllocateDuration(allocateDuration);
			}
		}
		return maintainPlanContentExtService.updateById(confirmExt);
	}


	@Override
	public Long addRescuePlan(MaintainMalfunctionRescue maintainMalfunctionRescue) {
		String url = projectApiConfiguration.getWeb() + "/admin/maintainPlanContent/add/rescue/plan";
		String res = null;
		try {
			res = HttpUtil.createPost(url).header(TokenConstant.HEADER, AuthUtil.getHeader(WebUtil.getRequest())).timeout(10000).body(JSONObject.toJSONString(maintainMalfunctionRescue)).execute().body();
		} catch (HttpException e) {
			log.error("请求梯无忧管理后台接口失败，url: " + url);
			throw new ServiceException("新增失败，请稍后再试");
		}

		JSONObject jsonObject = JSONObject.parseObject(res);
		if (jsonObject.getBoolean("success")) {
			long planId = jsonObject.getLongValue("data");
			Boolean ifSendMyself = maintainMalfunctionRescue.getIfSendMyself();
			if(Func.isNotEmpty(ifSendMyself) && ifSendMyself){
				PlanAddUserDTO planAddUserDTO = new PlanAddUserDTO();
				planAddUserDTO.setAddType(1);
				planAddUserDTO.setStatus(1);
				planAddUserDTO.setPlanId(planId);
				Long userId = AuthUtil.getUserId();
				List<Long> userIds = new ArrayList<>();
				userIds.add(userId);
				planAddUserDTO.setUserIds(userIds);
				this.planAddUser(planAddUserDTO);
			}
			return planId;
		} else {
			throw new ServiceException(jsonObject.getString("msg"));
		}
	}

	@Override
	public Boolean rescueCancel(Long planId) {
		String url = projectApiConfiguration.getWeb() + "/admin/maintain/malfunction/rescue/resuceCancel?planId=" + planId;
		String res = null;
		try {
			res = HttpUtil.createGet(url).header(TokenConstant.HEADER, AuthUtil.getHeader(WebUtil.getRequest())).timeout(10000).execute().body();
		} catch (HttpException e) {
			log.error("请求梯无忧管理后台接口失败，url: " + url);
			throw new ServiceException("调度失败，请稍后再试");
		}

		JSONObject jsonObject = JSONObject.parseObject(res);
		if (jsonObject.getBoolean("success")) {
			return jsonObject.getBoolean("data");
		} else {
			throw new ServiceException(jsonObject.getString("msg"));
		}
	}

	@Override
	public Boolean planAddUser(PlanAddUserDTO planAddUserDTO) {
		MaintainPlanContent maintainPlanContent = maintainPlanContentService.getById(planAddUserDTO.getPlanId());
		if (maintainPlanContent == null) {
			throw new ServiceException("未找到工单数据!");
		}
		planAddUserDTO.setStatus(maintainPlanContent.getStatus());
		String url = projectApiConfiguration.getWeb() + "/admin/maintain/malfunction/rescue/planAddUser";
		String res = null;
		try {
			res = HttpUtil.createPost(url).header(TokenConstant.HEADER, AuthUtil.getHeader(WebUtil.getRequest())).timeout(10000).body(JSONObject.toJSONString(planAddUserDTO)).execute().body();
		} catch (HttpException e) {
			log.error("请求梯无忧管理后台接口失败，url: " + url);
			throw new ServiceException("调度失败，请稍后再试");
		}

		JSONObject jsonObject = JSONObject.parseObject(res);
		if (jsonObject.getBoolean("success")) {
			return jsonObject.getBoolean("data");
		} else {
			throw new ServiceException(jsonObject.getString("msg"));
		}
	}

	@Override
	public PlanRescueDetailVO getRescueDetail(Long planId) {
		PlanRescueDetailVO planRescueDetail = baseMapper.getRescueDetail(planId);
		if (planRescueDetail == null) {
			throw new ServiceException("未找到相关数据!");
		}
		// 查询项目要求救援到达时间
		Integer rescueTime = buildingExtService.getBuildingRescueTime(planRescueDetail.getBuildingId(), planRescueDetail.getCreateTime());
		planRescueDetail.setRescueUseTime(rescueTime);
		return planRescueDetail;
	}

	@Override
	public List<MapUserInfoVO> mapRescueBuildingRecentlyUser(Long buildingId, List<String> vehicle, List<Integer> workStatus, Integer distance, List<Integer> isSign, Integer searchMin, Long elevatorId, String keyword) {
		MapRescueBuildingDetailVO detailVO = buildingService.selectMapRescueBuildingDetail(buildingId);
		// 在职人员列表
		TenantConfig tenantConfig = tenantConfigService.getTenantConfig(AuthUtil.getTenantId());
		String screenRoles = tenantConfig.getScreenRoles();
		List<MapUserInfoVO> userInfoVOList = baseMapper.mapUserList(screenRoles, keyword);
		// 筛选过滤
		userInfoVOList = this.mapUserDataFilter(userInfoVOList, vehicle, workStatus, isSign, searchMin, elevatorId);
		if (detailVO != null && detailVO.getLatitude() != null && detailVO.getLongitude() != null) {
			userInfoVOList.forEach(e -> e.setDistance(MapUtil.getDistance(Double.parseDouble(detailVO.getLongitude()), Double.parseDouble(detailVO.getLatitude()), Double.parseDouble(e.getLongitude()), Double.parseDouble(e.getLatitude()))));
		}
		if (Func.isNotEmpty(distance)) {
			userInfoVOList = userInfoVOList.stream().filter(e -> e.getDistance() == null || e.getDistance() <= distance).collect(Collectors.toList());
		}

		List<Long> mapUserIds = userInfoVOList.stream().map(t -> t.getUserId()).collect(Collectors.toList());
		Map<Long, Map<Integer, Long>> allUserPlanStatisticsMap = getPlanStatistics(mapUserIds);
		userInfoVOList.forEach(t -> {
			// 个人工单情况统计
			t.setPlanStatistics(allUserPlanStatisticsMap.get(t.getUserId()));
		});
		userInfoVOList = userInfoVOList.stream().sorted(Comparator.comparing(MapUserInfoVO::getDistance,
			Comparator.nullsFirst(Comparator.naturalOrder()))).collect(Collectors.toList());
		return userInfoVOList;
	}

	/**
	 * 地图人员数据筛选
	 *
	 * @param userInfoList
	 * @param vehicle      交通工具
	 * @param workStatus   工作状态
	 * @param isSign       签到
	 * @param searchMin    定位时间范围值
	 * @return
	 */
	private List<MapUserInfoVO> mapUserDataFilter(List<MapUserInfoVO> userInfoList, List<String> vehicle, List<Integer> workStatus, List<Integer> isSign, Integer searchMin, Long elevatorId) {
		if (Func.isNotEmpty(vehicle)) {
			userInfoList = userInfoList.stream().filter(e -> vehicle.contains(e.getVehicle())).collect(Collectors.toList());
		}
		if (Func.isNotEmpty(workStatus)) {
			userInfoList = userInfoList.stream().filter(e -> {
				for (Integer status : workStatus) {
					switch (status) {
						case 1:
							if (Func.isEmpty(e.getPlanTypes())) {
								return Boolean.TRUE;
							}
							break;
						case 2:
							if (Func.isNotEmpty(e.getPlanTypes()) && e.getPlanTypes().contains(MaintainPlanContentEnum.MAINTAIN_KR.getId())) {
								return Boolean.TRUE;
							}
							break;
						case 3:
							if (Func.isNotEmpty(e.getPlanTypes()) && e.getPlanTypes().contains(MaintainPlanContentEnum.MAINTAIN_WX.getId())) {
								return Boolean.TRUE;
							}
							break;
						case 4:
							if (Func.isNotEmpty(e.getPlanTypes()) && e.getPlanTypes().contains(MaintainPlanContentEnum.MAINTAIN_BY.getId())) {
								return Boolean.TRUE;
							}
							break;
					}
				}
				return Boolean.FALSE;
			}).collect(Collectors.toList());
		}
		if (Func.isNotEmpty(isSign) && isSign.size() == 1) {
			if (isSign.contains(1)) {
				userInfoList = userInfoList.stream().filter(e -> e.getIsSign()).collect(Collectors.toList());
			} else {
				userInfoList = userInfoList.stream().filter(e -> !e.getIsSign()).collect(Collectors.toList());
			}
		}
		if (Func.isNotEmpty(searchMin)) {
			Date tempDate = DateUtil.offsetMinute(DateUtil.date(), -searchMin);
			userInfoList = userInfoList.stream().filter(e -> DateUtil.compare(e.getCreateTime(), tempDate) >= 0).collect(Collectors.toList());
		}
		if (Func.isNotEmpty(elevatorId) && elevatorId != 0) {
			// 梯组
			ElevatorPrincipalDept principalDept = elevatorPrincipalDeptService.getOne(Wrappers.<ElevatorPrincipalDept>lambdaQuery().eq(ElevatorPrincipalDept::getElevatorId, elevatorId));
			if (principalDept != null) {
				userInfoList = userInfoList.stream().map(e -> {
					if (Func.isNotBlank(e.getDeptId())) {
						List<Long> deptIds = Func.toLongList(e.getDeptId());
						if (deptIds.contains(principalDept.getDeptId())) {
							e.setElevatorGroupUser(true);
						}
					}
					return e;
				}).collect(Collectors.toList());
			}
		}

		return userInfoList;
	}

	@Override
	public List<MapUserInfoVO> mapRescueBuildingRecentlyMaintainUser(Long buildingId, List<String> vehicle, List<Integer> workStatus, Integer distance, List<Integer> isSign, Integer searchMin, Long elevatorId, String keyword) {
		Building rescueBuilding = buildingService.getById(buildingId);
		if (Func.isEmpty(rescueBuilding)) {
			throw new ServiceException("数据不存在！");
		}

		List<Building> buildingList = buildingService.list();
		if (Func.isNotEmpty(distance)) {
			// 过滤出附近范围的项目
			buildingList = buildingList.stream().filter(t -> {
				int range = MapUtil.getDistance(rescueBuilding.getLongitude().doubleValue(), rescueBuilding.getLatitude().doubleValue(), t.getLongitude().doubleValue(), t.getLatitude().doubleValue());
				return range <= distance;
			}).collect(Collectors.toList());
		}
		if (Func.isEmpty(buildingList)) {
			return null;
		}

		List<Long> buildingIds = buildingList.stream().map(t -> t.getId()).collect(Collectors.toList());
		// 项目附近保养人员
		List<Long> userIds = baseMapper.selectBuildingRecentlyMaintainUser(buildingIds);
		if (Func.isEmpty(userIds)) {
			return null;
		}
		// 在职人员列表
		TenantConfig tenantConfig = tenantConfigService.getTenantConfig(AuthUtil.getTenantId());
		String screenRoles = tenantConfig.getScreenRoles();
		List<MapUserInfoVO> userInfoVOList = baseMapper.mapUserList(screenRoles, keyword).stream().filter(t -> userIds.contains(t.getUserId())).collect(Collectors.toList());
		if (Func.isEmpty(userInfoVOList)) {
			return null;
		}

		// 筛选过滤
		userInfoVOList = this.mapUserDataFilter(userInfoVOList, vehicle, workStatus, isSign, searchMin, elevatorId);
		List<Long> mapUserIds = userInfoVOList.stream().map(t -> t.getUserId()).collect(Collectors.toList());
		Map<Long, Map<Integer, Long>> allUserPlanStatisticsMap = getPlanStatistics(mapUserIds);
		userInfoVOList.forEach(t -> {
			// 个人工单情况统计
			t.setPlanStatistics(allUserPlanStatisticsMap.get(t.getUserId()));
			// 人员距项目距离
			t.setDistance(MapUtil.getDistance(Double.parseDouble(t.getLongitude()), Double.parseDouble(t.getLatitude()), rescueBuilding.getLongitude().doubleValue(), rescueBuilding.getLatitude().doubleValue()));
		});

		return userInfoVOList;
	}

	@Override
	public List<MapUserInfoVO> mapRescueBuildingRecentlyBuildingUser(Long buildingId, List<String> vehicle, List<Integer> workStatus, Integer distance, List<Integer> isSign, Integer searchMin, Long elevatorId, String keyword) {
		Building rescueBuilding = buildingService.getById(buildingId);
		if (Func.isEmpty(rescueBuilding)) {
			throw new ServiceException("数据不存在！");
		}

		// 在职人员列表
		TenantConfig tenantConfig = tenantConfigService.getTenantConfig(AuthUtil.getTenantId());
		String screenRoles = tenantConfig.getScreenRoles();
		List<MapUserInfoVO> userInfoVOList = baseMapper.mapUserList(screenRoles, keyword);
		if (Func.isEmpty(userInfoVOList)) {
			return null;
		}

		if (Func.isNotEmpty(distance)) {
			// 过滤出附近范围的人员
			userInfoVOList = userInfoVOList.stream().filter(t -> {
				if (Func.isBlank(t.getResidentialLongitude()) || Func.isBlank(t.getResidentialLatitude())) {
					return false;
				}
				int range = MapUtil.getDistance(rescueBuilding.getLongitude().doubleValue(), rescueBuilding.getLatitude().doubleValue(), Double.parseDouble(t.getResidentialLongitude()), Double.parseDouble(t.getResidentialLatitude()));
				t.setDistance(range);
				return range <= distance;
			}).collect(Collectors.toList());
		}

		// 筛选过滤
		userInfoVOList = this.mapUserDataFilter(userInfoVOList, vehicle, workStatus, isSign, searchMin, elevatorId);
		List<Long> mapUserIds = userInfoVOList.stream().map(t -> t.getUserId()).collect(Collectors.toList());
		Map<Long, Map<Integer, Long>> allUserPlanStatisticsMap = getPlanStatistics(mapUserIds);
		userInfoVOList.forEach(t -> {
			// 个人工单情况统计
			t.setPlanStatistics(allUserPlanStatisticsMap.get(t.getUserId()));
		});

		return userInfoVOList;
	}

	/**
	 * 个人工单情况统计数据封装
	 *
	 * @param userIds
	 * @return
	 */
	private Map<Long, Map<Integer, Long>> getPlanStatistics(List<Long> userIds) {
		Map<Long, Map<Integer, Long>> allUserPlanStatistics = new HashMap<>(16);
		if (Func.isEmpty(userIds)) {
			return allUserPlanStatistics;
		}
		// 个人工单情况统计
		List<MaintainPlanContent> planContentList = maintainPlanContentService.screenUserPlanStatisticsList(userIds);
		Map<Long, List<MaintainPlanContent>> planContentMap = planContentList.stream().collect(Collectors.groupingBy(t -> t.getUserId()));
		planContentMap.forEach((k, v) -> {
			Map<Integer, Long> planStatistics = new HashMap<>(4);
			// 已完成保养工单数
			planStatistics.put(0, v.stream().filter(t -> t.getTypeStatus() == 1 && t.getStatus() >= 3).count());
			// 总保养数
			planStatistics.put(1, v.stream().filter(t -> t.getTypeStatus() == 1).count());
			// 尚未完成维修数
			planStatistics.put(2, v.stream().filter(t -> t.getTypeStatus() == 2).count());
			// 尚未完成救援数
			planStatistics.put(3, v.stream().filter(t -> t.getTypeStatus() == 3).count());
			allUserPlanStatistics.put(k, planStatistics);
		});
		return allUserPlanStatistics;
	}

	@Override
	public IPage<DispatcherPageRespVO> dispatcherPage(DispatcherPageReqVO dispatcherPageReqVO, IPage<DispatcherPageRespVO> page) {
		return baseMapper.dispatcherPage(dispatcherPageReqVO, page);
	}

	@Override
	public DispatcherStatisticsVO dispatcherStatistics() {
		DispatcherStatisticsVO statisticsVO = new DispatcherStatisticsVO();
		IPage<DispatcherPageRespVO> emptyPage = new Page<>(1, -1);
		IPage<DispatcherPageRespVO> page = baseMapper.dispatcherPage(new DispatcherPageReqVO(),emptyPage);
		List<DispatcherPageRespVO> list = page.getRecords();
		if (Func.isNotEmpty(list)) {
			statisticsVO.setDispatching(list.stream().filter(o -> o.getStatus().equals(TaskStatusEnum.JY_DISPATCH.getStatus())).count());
			// 已调度状态, 2025-09 去除已完成状态，TaskStatusEnum.JY_RESCUE.getStatus()
			Integer[] dispatchedStatus = new Integer[]{TaskStatusEnum.JY_TO_START.getStatus(), TaskStatusEnum.JY_PROCESSED.getStatus(), TaskStatusEnum.JY_ARRIVE.getStatus()};
			statisticsVO.setDispatched(list.stream().filter(o -> Arrays.asList(dispatchedStatus).contains(o.getStatus())).count());
			statisticsVO.setTotal(list.size());
		}
		return statisticsVO;
	}

	@Override
	public List<RescueDetailVO> rescueDetailListByElevatorId(Long buildingId, Long elevatorId) {
		List<RescueDetailVO> list = baseMapper.rescueDetailListByElevatorId(buildingId, elevatorId);
		return MaintainPlanContentWrapper.build().convertListByStatus(list);
	}

	@Override
	public Boolean hasRelaMalOrder(Long id) {
		return baseMapper.hasRelaMalOrder(id);
	}

	@Override
	public HashMap<String, Object> reportMal(RepairWorkVO repairWorkVO) {
		if (hasRelaMalOrder(repairWorkVO.getFromPlanId())) {
			throw new ServiceException("已存在关联的维修工单");
		}
		return maintainMalfunctionWorkService.malfunctionWorkSave(repairWorkVO);
	}

	@Override
	public Boolean reminder(Long planId) {
		MaintainPlanContent planContent = maintainPlanContentService.getById(planId);
		if (planContent == null) {
			throw new ServiceException("未找到对应工单");
		}

		// 获取当前日期，格式：yyyy-MM-dd
		String today = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
		String reminderKey = RESCUE_REMINDER_KEY.concat(StringPool.DASH).concat(String.valueOf(planId)).concat(StringPool.DASH).concat(today);

		// 检查今天的催办次数
		Long count = bladeRedis.incr(reminderKey);

		// 如果是第一次设置key，设置过期时间为当天结束（第二天凌晨）
		if (count == 1) {
			LocalDateTime tomorrow = LocalDateTime.now().plusDays(1).withHour(0).withMinute(0).withSecond(0).withNano(0);
			long expireSeconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), tomorrow);
			bladeRedis.expire(reminderKey, expireSeconds);
		}

		// 检查是否超过每天3次的限制
		if (count > 3) {
			throw new ServiceException("今日催办次数已达上限，请明天再试");
		}

		if (planContent.getStatus() != TaskStatusEnum.JY_RESCUE.getStatus() &&
			planContent.getStatus() != TaskStatusEnum.JY_CANCEL.getStatus()) {
			MaintainMalfunctionRescue malfunctionRescue = this.getOne(Wrappers.<MaintainMalfunctionRescue>lambdaQuery().eq(MaintainMalfunctionRescue::getPlanId, planId), false);
			if (malfunctionRescue != null && malfunctionRescue.getArrivalWarning() != null && malfunctionRescue.getArrivalWarning() > 0) {
				TriggerConditionEnum triggerConditionEnum = null;
				RescueArriveEnum rescueArriveEnum = RescueArriveEnum.getEnum(malfunctionRescue.getArrivalWarning());
				switch (rescueArriveEnum) {
					case UPCOMING:
						triggerConditionEnum = TriggerConditionEnum.CONDITION_20;
						break;
					case OVERTIME:
						triggerConditionEnum = TriggerConditionEnum.CONDITION_21;
						break;
					case OVERTIME_ARRIVE:
						triggerConditionEnum = TriggerConditionEnum.CONDITION_25;
						break;
					case OVERTIME_NOT_DISPATCH:
						triggerConditionEnum = TriggerConditionEnum.CONDITION_19;
						break;
					case OVERTIME_NOT_DEPART:
						triggerConditionEnum = TriggerConditionEnum.CONDITION_24;
						break;
					default:
						break;
				}

				if (triggerConditionEnum != null) {
					// 消息通知
					eventPublisher.publishEvent(new NoticeEvent(NoticeTaskParam.builder()
						.businessType(BusinessType.RESCUE.getCode())
						.triggerNode(triggerConditionEnum.getTriggerNode())
						.triggerCondition(triggerConditionEnum.getCode())
						.bizId(planContent.getId())
						.tenantId(planContent.getTenantId())
						.build()
					));
				}
			}
		}
		return true;
	}
}
